package com.cseg674.datamanagement.adler;

import com.hp.hpl.jena.db.IDBConnection;
import com.hp.hpl.jena.db.ModelRDB;
import com.hp.hpl.jena.db.RDFRDBException;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.reasoner.Reasoner;
import com.hp.hpl.jena.reasoner.ReasonerRegistry;
import com.hp.hpl.jena.shared.Lock;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

/**
 * DBModel acts as a model manager for a JDBC database connection.
 */
public class DBModel
{
   /**
    * DBModel Constructor
    *
    * @param dbConnection   Connection control from the database.
    * @param defaultModel   The default database model created with just the
    *                       ontology.  Usually, "default".
    * @param ontology       The base ontology URI.
    */
    public DBModel(IDBConnection dbConnection, String defaultModel,
        String ontology) throws AdlerException
    {

        this.dbConnection = dbConnection;
        this.ontology     = ontology;
        
        if( !checkIfModelExists(defaultModel)) {
            createInferenceModel();
            createModel(defaultModel);
        }
        else {
            selectModel(defaultModel);
        }
        
        this.defaultModel = defaultModel;
    }

   /**
    * Closes the ModelRDB object. Children should override this method (and
    * always call it first).
    */
    public void shutdown()
    {
        rdbModel.close();
    }

   /**
    * Returns a reference to the ModelRDB object
    *
    * @return rdbModel  Reference to the ModelRDB associated with the project
    *                   database.
    */
    public Model getModel()
    {
        return rdbModel;  
    }

   /**
    * Returns a reference to the Inference Model associated with a particular
    * project.
    *
    * @param  projectName   Name of the project whose inference model is
    *                       returned.
    * @return infModel      The inference model associated with "projectName"
    */
    public InfModel getInfModel(String projectName) throws AdlerException
    {
        selectModel(projectName);
        return infModel;
    }

   /**
    * Generate a list of all model names and return an iterator.
    */
    public ExtendedIterator listModels() {
        return dbConnection.getAllModelNames();
    }

   /**
    * Check for a model's existence.
    *
    * @param  projectName   Name of the project to check for existence.
    * @return boolean       True if model exists; false otherwise.
    */
    public boolean checkIfModelExists(String projectName) throws AdlerException
    {
        
        try {
            if(dbConnection.containsModel(projectName)) {
                return true;
            }
        } catch(RDFRDBException ex) {
            throw new AdlerException("Database connection error.");
        } catch(Throwable ex) {
            throw new AdlerException("Database connection error.");
        }

        return false;
        
    }

   /**
    * Create a new ModelRDB object and return a reference to it.
    *
    * @param  projectName   Project name used to reference the new model.
    * @return rdbModel      Reference to the new ModelRDB object.
    */
    public Model createModel(String projectName) throws AdlerException
    {
//        locks anywhere?
           try{
		     rdbModel = ModelRDB.createModel(dbConnection, projectName);
		   }
		   catch(Exception e){
		     throw new AdlerException("Error creating model: " + projectName);
		   }
        return rdbModel;
    }

   /**
    * Select an existing model in the database.
    *
    * @param projectName    Name of the project to be selected.
    */
    private void selectModel(String projectName) throws AdlerException {
         
        // check if projectName is not currently selected project
        if (!selectedProject.equals(projectName)) {

            try {
                rdbModel = ModelRDB.open(dbConnection, projectName);
            } catch (RDFRDBException e) {
                throw new AdlerException("Error selecting project: "
                    + projectName);
            }

            createInferenceModel();

            selectedProject = projectName;

        }
    }

   /**
    * Delete a specific model from the database.
    *
    * @param projectName    Name of the project to be deleted.
    */
    public void deleteModel(String projectName) throws AdlerException {

        selectModel(projectName);
        
        rdbModel.enterCriticalSection(Lock.READ);

        try {
            rdbModel = ModelRDB.open(dbConnection, projectName);

            rdbModel.remove();

        } finally { 
//            rdbModel.leaveCriticalSection();
        }
        selectModel(defaultModel);
    }

   /**
    * Build an inference model for the selected model.
    */
    private void createInferenceModel() throws AdlerException {
        
        try {
            Reasoner r = ReasonerRegistry.getOWLMiniReasoner();

            OntModelSpec spec = new OntModelSpec( OntModelSpec.OWL_DL_MEM );

            spec.setReasoner( r );

            OntModel m = ModelFactory.createOntologyModel( spec, null );
          
            // load an input document
            m.read(ontology);

            // should a lock be here?? -sentella
            infModel = ModelFactory.createInfModel(r, m, rdbModel);

        } catch(Throwable t) {
            throw new AdlerException("Error creating inference model");
        }
    }
    

    // Current Selected Model backed by Database
    private ModelRDB      rdbModel          = null;
    // Inference Model associated with current selected project
    private InfModel      infModel          = null;
    private String        selectedProject   = "";
    private String        defaultModel      = "";
    private IDBConnection dbConnection      = null;
    private String        ontology          = null;
}